#!/usr/bin/python
# -*- encoding: utf-8 -*-

name="xch2gcal"
desc="Exchange/Davmail to Google Calendar"
version="0.1"
versionname="hackv1"

import hashlib
import os
import sys
import glob
from optparse import OptionParser
import logging
from logging.handlers import SysLogHandler
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(name)
logger.setLevel(logging.INFO)
syslog = SysLogHandler(address='/dev/log',facility="local4")
formatter = logging.Formatter('%(name)s: %(levelname)s %(message)s')
syslog.setFormatter(formatter)
logger.addHandler(syslog)
from icalendar import Calendar, Event
import datetime
import base64
from pytz import reference
from subprocess import Popen, PIPE, STDOUT
import re
import urllib2
import pycurl

def execute(command):

	p = Popen(command, shell=True, stdin=PIPE, stdout=PIPE, stderr=PIPE, close_fds=True)
	stdOut=re.sub('\\n+','\\n',str(p.stdout.read().decode("utf-8"))).split("\n")
	stdErr=str(p.stderr.read()).split("\n")

	logger.info(stdOut)
	
	if stdErr != "":
		logger.error(stdErr)

	return stdOut, stdErr


### download ics file from davmail/exchange server

def downloadIcs(email,username,password,url,workingdir,runts):

	if not os.path.isdir(workingdir):
		os.makedirs(workingdir)

	downloaddir=workingdir+"/downloads"

	if not os.path.isdir(downloaddir):
		os.makedirs(downloaddir)

	_oldfiles = glob.glob(downloaddir+os.sep+"*.ics")

	for f in _oldfiles:
		logger.info("deleting old downloaded ics-file: "+f)
		os.remove(f)

	logger.info("downloading new ics-file.")
	request = urllib2.Request(url)
	base64string = base64.encodestring('%s:%s' % (username, password)).replace('\n', '')
	request.add_header("Authorization", "Basic %s" % base64string)   
	result = urllib2.urlopen(request)


	fp = open(downloaddir+"/xchevents_"+str(runts)+".ics", 'wb')
	for line in result:
	   fp.write(line)
	fp.close()

	logger.info("written new ics-file to: "+downloaddir+"/xchevents_"+str(runts)+".ics")

	if os.path.exists(downloaddir+"/xchevents_"+str(runts)+".ics"):
		return downloaddir+"/xchevents_"+str(runts)+".ics"
	else:
		return "/dev/null"	


def getGoogleAddCmdStr(action,gcalname,gdate,geventname=None,gstartts=None,gtzname=None,gendts=None,geventlocation=None,geventdesc=None):

	gstr=""

	# minutes
	duration="30"
	reminder="0"

	if gstartts != None and gendts != None:
		try:
			_rgstartts = datetime.datetime.strptime(gstartts,"%I:%M %p")
			_rgendts = datetime.datetime.strptime(gendts,"%I:%M %p")

		 	duration = str((_rgendts-_rgstartts).seconds  / 60)

			if int(duration) <= 30:
				duration="10"

		except:
			
			logger.warn("duration calculation failed.")


	if action == "add":
	#	gstr="google calendar add --cal=\""+gcalname+"\" \""+geventname+" at "+geventlocation+" "+gdate+" "+str(gstartts)+" "+gtzname+" for "+str(duration)+" minutes\""
		gstr="gcalcli --cal \""+gcalname+"\" --title \""+geventname+"\" --where \""+geventlocation+"\" --when \""+gdate+" "+str(gstartts)+"\" --duration \""+duration+"\" --desc \""+geventdesc+"\" --reminder \""+reminder+"\" add"

	elif action == "delete":

		gstr="google calendar delete --cal \""+gcalname+"\" --date "+gdate+" --title \".*\" --yes"

	return gstr

def executeGoogleCalCmd(command):

	logger.info(command)
	execute(command)

def getRelevantEvents(icsfile,workingdir,gcalname):

	syncstatedir=workingdir+"/syncstate"

	if not os.path.isdir(syncstatedir):
		os.makedirs(syncstatedir)

	g = open(icsfile,'rb')
	cal = Calendar.from_ical(g.read())
	todayts=str(datetime.datetime.utcnow().strftime("%Y%m%d"))

	hashdict={}
	eventdict={}
	walkagaindays=[]

	for component in cal.walk():
		if component.name == "VEVENT":

			eventstart = component.get('dtstart').dt
			eventend = component.get('dtend').dt
			now = datetime.datetime.today()
			eventday=str(eventstart.strftime("%Y-%m-%d"))


    
			localtime = reference.LocalTimezone()

			if float(eventstart.strftime('%s')) - float(now.strftime('%s')) > 0:
				_geventname="x: "+component.get('summary')
				_geventlocation=component.get('location')
				_gstartts=eventstart.strftime("%I:%M %p")
				_gendts=eventend.strftime("%I:%M %p")
       				_tzname=localtime.tzname(now)

				eventdesc = component.get('DESCRIPTION')
				_attendee = component.get('ATTENDEE')
				if _attendee:
					for _ae in _attendee:
						eventdesc = eventdesc+"\n"+_ae

				if not _geventlocation:
					_geventlocation="unknown"
				if not _gendts:
					_gendts = datetime.datetime.strptime("11:59 pm","%I:%M %p")
				if not _tzname:
					_tzname="UTC"

				if not eventdesc: 
					eventdesc=""
				
				logger.info("found event in future: "+_geventname+" "+_gstartts)

				if hashdict.has_key(eventday):
					hashdict[eventday]+=_geventname+_geventlocation+_gstartts+_gendts+_tzname+eventdesc
				else:
					hashdict[eventday]=_geventname+_geventlocation+_gstartts+_gendts+_tzname+eventdesc

				if eventdict.has_key(eventday):
					eventdict[eventday].append(getGoogleAddCmdStr("add",gcalname,eventday,_geventname,_gstartts,_tzname,_gendts,_geventlocation,eventdesc))
				else:
					eventdict[eventday]=[getGoogleAddCmdStr("add",gcalname,eventday,_geventname,_gstartts,_tzname,_gendts,_geventlocation,eventdesc)]

					



	


	for h in hashdict:
		mh = hashlib.md5()
		mh.update(hashdict[h].encode("utf-8"))
		_hash = mh.hexdigest()

		if not os.path.exists(syncstatedir+os.sep+h):
			
			fp = open(syncstatedir+os.sep+h, 'wb')
	   		fp.write(_hash)
			fp.close()
			logger.info("written new syncstate file for: "+h)

			if eventdict.has_key(h):
				for ne in eventdict[h]:
					executeGoogleCalCmd(ne)

		else:
			logger.info("reading syncstate file for: "+h)
			efp = open(syncstatedir+os.sep+h, 'r')
			existinghash = efp.readline()
			efp.close()

			if _hash == existinghash:
				
				logger.info("syncstate hash identical for: "+h)
			else:
				logger.info("syncstate hash changed resyncing: "+h)

				_gcmd = getGoogleAddCmdStr("delete",gcalname,h)

				executeGoogleCalCmd(_gcmd)	

                  	      	if eventdict.has_key(h):
                                	for ne in eventdict[h]:
                                        	executeGoogleCalCmd(ne)


		
					
	
    
	g.close()

def getPassword(pwfile):
		if os.path.exists(pwfile):
			efp = open(pwfile, 'r')
			pw = efp.readline()
			pw = pw.strip()
			efp.close()
			return pw
		else:
			logger.error("password file does not exist.")
			sys.exit(2)

def main():

        parser = OptionParser()
        parser.add_option("-v", "--verbose", action="store_true", dest="verbose", default=False, help="show verbose")
	parser.add_option("-p", "--passwordfile", dest="passwordfile", help="password file")
	parser.add_option("-e", "--email", dest="email", help="exchange email address")
	parser.add_option("-u", "--username", dest="username", help="exchange/windows user name")
	parser.add_option("-c", "--caldav-url", dest="caldavurl", help="caldav url, you can use $EMAIL$ as replacement variable")
	parser.add_option("-g", "--google-calendar-name", dest="gcalname", help="name of the google calendar")

	(options, args) = parser.parse_args()

        if options.verbose:
		logger.setLevel(logging.DEBUG)

	passwordfile=os.environ['HOME']+os.sep+".pw/xch2gcal"
	if options.passwordfile:
		passwordfile=options.passwordfile


	runts = datetime.datetime.utcnow().strftime("%Y%m%d%H%M%S%f")

	email="none@localhost"
	if options.email:
		email=options.email


	username="none"
	if options.username:
		username=options.username

	caldavurl="http://localhost"
        if options.caldavurl:
		caldavurl=options.caldavurl.replace("$EMAIL$",email)

	gcalname=""
	if options.gcalname:
		gcalname=options.gcalname

	logger.info("using username: "+username)
	logger.info("using email: "+email)
	logger.info("using caldav-url: "+caldavurl)
	logger.info("using google-calendar: "+gcalname)
	
		
	password=getPassword(passwordfile)

	workingdir="/var/lib/"+name

	icsfile = downloadIcs(email,username,password,caldavurl,workingdir,runts)

	getRelevantEvents(icsfile,workingdir,gcalname)





if __name__=='__main__': main()
